using System.Collections.Specialized;
using System.ComponentModel;
using System.Reflection;

namespace Viyi.Util;

/**
 * <summary>描述一个枚举值的信息</summary>
 */
public record EnumEntry<E> where E : Enum {
    public EnumEntry(E value, string? name = null, string? description = null) {
        Value = value;
        Name = name ?? value.ToString();
        Description = description;
    }

    [Obsolete("使用 EnumEntry(E, string?, string?) 代替")]
    public EnumEntry(string name, E value, string? description) {
        Name = name;
        Value = value;
        Description = description;
    }

    /** <summary>枚举值的名称</summary> */
    public string Name { get; set; }

    /** <summary>枚举值</summary> */
    public E Value { get; set; }

    /**
     * <summary>枚举值的说明</summary>
     * <remarks>枚举值的说明通常是由 `System.ComponentModel.DescriptionAttribute` 描述的</remarks>
     */
    public string? Description { get; set; }
}

public static class EnumHelper {
    public static IEnumerable<T> EnumerateWith<T, E>(Func<string, E, T> mapper) where E : Enum {
        return Enum.GetValues(typeof(E))
            .Cast<E>()
            .Select(v => mapper(v.ToString(), v));
    }

    public static IEnumerable<T> EnumerateWith<T, E>(Func<string, E, FieldInfo, T> mapper) where E : Enum {
        return Enum.GetValues(typeof(E))
            .Cast<E>()
            .Select(v => mapper(
                v.ToString(),
                v,
                v.GetType().GetField(v.ToString())!
            ));
    }

    public static IEnumerable<EnumEntry<E>> EnumerateWithDescription<E>()
        where E : Enum {
        return EnumerateWith<EnumEntry<E>, E>((name, value, field) => new EnumEntry<E>(
            value,
            name,
            field.GetCustomAttribute<DescriptionAttribute>(false)?.Description
        ));
    }

    public static string? GetDescription(Enum value) {
        return value.GetType().GetField(value.ToString())
            ?.GetCustomAttribute<DescriptionAttribute>(false)
            ?.Description;
    }

    /// <summary>
    /// 将枚举类型的各项转换成一个可遍历的序列
    /// </summary>
    /// <param name="enumType"></param>
    /// <returns></returns>
    public static IEnumerable<KeyValuePair<string, object>> Enumerate(Type enumType) {
        CheckEnumType(enumType);
        return Enum.GetValues(enumType)
            .Cast<object>()
            .Select(v => new KeyValuePair<string, object>(v.ToString()!, v));
    }

    /// <summary>
    /// 将枚举类型的各项转换成一个可遍历的序列
    /// </summary>
    /// <typeparam name="E"></typeparam>
    /// <returns></returns>
    public static IEnumerable<KeyValuePair<string, E>> Enumerate<E>()
        where E : Enum {
        return Enum.GetValues(typeof(E))
            .Cast<E>()
            .Select(v => new KeyValuePair<string, E>(v!.ToString()!, v));
    }

    /// <summary>
    /// 将枚举类型的各项转换成一个可遍历的序列
    /// </summary>
    /// <typeparam name="TUnderlying"></typeparam>
    /// <param name="enumType"></param>
    /// <returns></returns>
    public static IEnumerable<KeyValuePair<string, TUnderlying>> Enumerate<TUnderlying>(Type enumType) {
        CheckEnumType(enumType);
        return Enum.GetValues(enumType)
            .Cast<object>()
            .Select(v => new KeyValuePair<string, TUnderlying>(v.ToString()!, (TUnderlying) v));
    }

    /// <summary>
    /// 将枚举类型的各项转换成一个可遍历的序列
    /// </summary>
    /// <typeparam name="E"></typeparam>
    /// <typeparam name="TUnderlying"></typeparam>
    /// <returns></returns>
    public static IEnumerable<KeyValuePair<string, TUnderlying>> Enumerate<E, TUnderlying>()
        where E : Enum => Enumerate<TUnderlying>(typeof(E));

    /// <summary>
    /// 将枚举类型的各项转换成一个 <c>NameValueCollection</c>，
    /// 由参数 <c>isNameAsKey</c> 决定其 Key 是枚举名称还是枚举数值。
    /// </summary>
    /// <param name="enumType"></param>
    /// <param name="isNameAsKey"></param>
    /// <remark>当 <c>isNameAsKey == false</c> 时，可能会出现重复的 Key，
    /// 这种情况下应该谨慎使用生成的 <c>NameValueCollection</c>，
    /// 可以使用其 <c>GetValues().First()</c> 来获取唯一值则不是逗号分隔的所有值。</remark>
    /// <returns></returns>
    public static NameValueCollection ToNameValueCollection(Type enumType, bool isNameAsKey) {
        var all = Enumerate(enumType);
        var unType = Enum.GetUnderlyingType(enumType);

        var valueGetter = new Func<object, string>(v => Convert.ChangeType(v, unType).ToString()!);
        var collection = new NameValueCollection();

        return isNameAsKey
            ? all.Aggregate(collection, (c, e) => {
                c.Add(e.Key, valueGetter(e.Value));
                return c;
            })
            : all.Aggregate(collection, (c, e) => {
                c.Add(valueGetter(e.Value), e.Key);
                return c;
            });
    }

    public static NameValueCollection ToNameValueCollection<TEnum>(bool isNameAsKey)
        where TEnum : Enum => ToNameValueCollection(typeof(TEnum), isNameAsKey);

    private static void CheckEnumType(Type type) {
        if (!type.IsEnum) {
            throw new ArgumentException($"specified type [{type.FullName}] is not a enum type");
        }
    }
}
